package HTMLParser;

import HTMLParser.HTMLTag.*;
import Tree.*;
import Errors.*;
import Parser.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.io.*;
import java.lang.reflect.Constructor;

/**
 * This class builds a tree representation of HTML tags based on the tags recieved 
 * from the HTMLLexicalAnalyser.
 * @author Nathan Scully
 * @version FINAL
 */
public class HTMLTreeGenerator implements TreeGenerator {

    private String file;
    private ErrorLog log;
    private Properties props;

    /**
     * The Constructor for HTMLTreeGenerator, which sets the varibles to be used by generateTree()
     * and loads the necessary properites file.
     * @param file The file to be passed to the HTMLLexicalAnalyser (when it is created)
     * @param log The common errorLog used by the whole program.
     */
    public HTMLTreeGenerator(String file, ErrorLog log) {
        this.file = file;
        this.log = log;
        Node.setErrorLog(log);
        ReadFile.setErrorLog(log);
        props = new Properties();

        try {
            props.load(new FileInputStream(System.getProperty("user.dir") + System.getProperty("file.separator") + "HTMLParser.properties"));
        } catch (IOException ex) {
            this.log.logError("Couldn't find HTMLParser.properties file.");
        }
    }

    /**
     * This method uses a HTMLLexicalAnalyser to break the file into tags, then
     * arranges those tags into a tree of Node objects.
     * @return The tree of tags that has been generated as a Tree object.
     */
    public Tree generateTree() {
        HTMLLexicalAnalyser lexer = new HTMLLexicalAnalyser(file);
        Tree tree;
        Token token = null;
        String tokenString = "";

        if (lexer.hasToken()) {
            token = lexer.getToken();
            tokenString = token.getToken();
        }
        if (!tokenString.toLowerCase().equals("html")) {
            log.logError("Missing or incorrect <html> tag at start of file.");
        // If there is no HTML tag, then an error is logged, but the token
        // that was found instead is kept.
        } else {
            token = null; // If there was an HTML tag, token becomes null as a flag value.
        }

        // A new HTML node is created anyway, regardless of whether one was found or not.
        tree = new Tree(new HTML(new Token("<html>", "OPEN")));
        Node currentNode = tree.getRoot();
        Class[] paramTypes = {Token.class};

        //The, if there was an HTML tag, token will be null at this point.
        //If not, the actual first tag will still be stored in token.
        while (lexer.hasToken()) {
            if (token == null) //Hence, if token is null, get the next token.
            {
                token = lexer.getToken();
            }

            tokenString = getTokenProperty(token.getToken().toLowerCase());
            String propString = props.getProperty(getTokenProperty(tokenString));
            if (token.getType().equals("TEXT")) //If the tag is a text tag, just create a text Node now.
            {
                currentNode.setChild(new Text(token.getToken()));
            } else //Otherwise, consider each token's type to see how the tree should treat it.
            {
                try {
                    Class tagClass = Class.forName(propString);
                    if (token.getType().equals("OPEN")) {
                        Constructor cons = tagClass.getConstructor(paramTypes);
                        Object[] params = {token};
                        Node node = (Node) cons.newInstance(params);
                        currentNode.setChild(node);
                        node.setParent(currentNode);
                        currentNode = node;
                    } else if (token.getType().equals("CLOSE")) {
                        String currentType = getTokenProperty(currentNode.getToken().toLowerCase());
                        if (tokenString.equals(currentType)) {
                            currentNode = currentNode.getParent();
                        }
                    } else if (token.getType().equals("SELF_CLOSE")) {
                        Constructor cons = tagClass.getConstructor(paramTypes);
                        Object[] params = {token};
                        Node node = (Node) cons.newInstance(params);
                        currentNode.setChild(node);
                    } else {
                        // This should be an HTML comment, so do nothing, as
                        // HTML comments should not be processed by the tree.
                    }
                } catch (ClassNotFoundException ex) { //Most of these exceptions will never happen.
                } catch (NoSuchMethodException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (IllegalArgumentException ex) {
                } catch (InvocationTargetException ex) {
                } catch (NullPointerException ex) {
                    log.logError("Tag " + tokenString + " not found in properties file.");
                }
            }
            token = null; //token is set to null at the end to be consistant
        // with the first if statement at the beginning of this while loop.
        }
        return tree;
    }

    /**
     * This method takes a token's string and trims the parameters from it.
     * ie: Image tags are img src="image", so this method removes the "src="
     * parameter returns just "img".
     * @param tokenString The string to be trimmed.
     * @return The new String representing the tag without the parameters.
     */
    private String getTokenProperty(String tokenString) {
        if (tokenString.equals("<html>")) {
            return "html";
        }
        int i = 0;
        while (i < tokenString.length() && tokenString.charAt(i) != ' ') {
            i++;
        }
        return tokenString.substring(0, i);
    }
}